package com.fjec.common.util;

import com.google.common.base.Strings;

import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.*;
import java.security.GeneralSecurityException;
import java.security.SecureRandom;
import java.time.temporal.ChronoUnit;
import java.util.Date;
import java.util.Random;

/**
 * Random Number Generator (RNG) 未指定的情况下 Android 2.3 以上版本使用的 随机数序列是 Android's OpenSSL-backed security provider
 * 以下版本是 BouncyCastle Security Provider
 * <p>
 * JDK 1.7 内没有这个Provider, 这个Android自己搞的, 服务端解不出来
 * <p>
 * 目前Android支持的 Random Number Generator (RNG) 有以下几种
 * <p>
 * Android's OpenSSL-backed security provider 1ASN.1, DER, PkiPath, PKCS7
 * BouncyCastle Security Provider v1.49 HARMONY (SHA1 digest; SecureRandom; SHA1withDSA signature) Harmony JSSE Provider Android KeyStore security provider
 * <p>
 * 服务端如果也没指定的话，默认使用的是 sun.security.provider.Sun
 * Oracle JDK 1.7 环境下支持的 Random Number Generator (RNG) 有以下几种
 * <p>
 * security.provider.1=sun.security.provider.Sun
 * security.provider.2=sun.security.rsa.SunRsaSign
 * security.provider.3=com.sun.net.ssl.internal.ssl.Provider
 * security.provider.4=com.sun.crypto.provider.SunJCE
 * security.provider.5=sun.security.jgss.SunProvider
 * security.provider.6=com.sun.security.sasl.Provider
 * security.provider.7=org.jcp.xml.dsig.internal.dom.XMLDSigRI
 * security.provider.8=sun.security.smartcardio.SunPCSC
 * security.provider.9=sun.security.mscapi.SunMSCAPI
 */

public class AESUtil {

    private static final String AES = "AES";
    // 随机算法
    private static final String SHA1PRNG = "SHA1PRNG";
    private static final String AES_CBC_PKCS5Padding = "AES/CBC/PKCS5Padding";
    // 密钥偏移量(非ECB模式下)
//	private static final String IV = "1234567890123456";
    private static final String UTF_8 = "UTF-8";

    private AESUtil() {
    }

    public static String encode(String str, String key) {
        if (!Strings.isNullOrEmpty(str)) {
            try {
                Cipher cipher = Cipher.getInstance(AES_CBC_PKCS5Padding);
                cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec(key), new IvParameterSpec(generateIV(key).getBytes(UTF_8)));
                byte[] bytes = cipher.doFinal(str.getBytes(UTF_8));
                return Base64Util.encode(bytes);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return null;
    }

    public static String decode(String str, String key) {
        if (!Strings.isNullOrEmpty(str)) {
            try {
                Cipher cipher = Cipher.getInstance(AES_CBC_PKCS5Padding);
                cipher.init(Cipher.DECRYPT_MODE, secretKeySpec(key), new IvParameterSpec(generateIV(key).getBytes(UTF_8)));
                byte[] bytes = cipher.doFinal(Base64Util.decode2Bytes(str));
                return new String(bytes, UTF_8);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return null;
    }

    public static void encodeFile(String srcFilePath, String destFilePath, String key) {
        InputStream in = null;
        OutputStream out = null;
        try {
            Cipher cipher = Cipher.getInstance(AES_CBC_PKCS5Padding);
            cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec(key), new IvParameterSpec(generateIV(key).getBytes(UTF_8)));

            in = new FileInputStream(srcFilePath);
            out = new FileOutputStream(validateFile(destFilePath));
            crypto(in, out, cipher);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                if (in != null) in.close();
                if (out != null) out.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    public static void decodeFile(String srcFilePath, String destFilePath, String key) {
        InputStream in = null;
        OutputStream out = null;
        try {
            Cipher cipher = Cipher.getInstance(AES_CBC_PKCS5Padding);
            cipher.init(Cipher.DECRYPT_MODE, secretKeySpec(key), new IvParameterSpec(generateIV(key).getBytes(UTF_8)));

            in = new FileInputStream(srcFilePath);
            out = new FileOutputStream(validateFile(destFilePath));
            crypto(in, out, cipher);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                if (in != null) in.close();
                if (out != null) out.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    private static File validateFile(String filePath) throws IOException {
        File file = new File(filePath);
        if (!file.getParentFile().exists()) file.getParentFile().mkdirs();
        if (file.exists()) file.delete();
        file.createNewFile();
        return file;
    }

    private static void crypto(InputStream in, OutputStream out, Cipher cipher) throws IOException, GeneralSecurityException {
        int blockSize = cipher.getBlockSize() * 1000;
        int outputSize = cipher.getOutputSize(blockSize);

        byte[] inBytes = new byte[blockSize];
        byte[] outBytes = new byte[outputSize];

        int inLength = -1;
        boolean more = true;
        while (more) {
            inLength = in.read(inBytes);
            if (inLength == blockSize) {
                int outLength = cipher.update(inBytes, 0, blockSize, outBytes);
                out.write(outBytes, 0, outLength);
            } else {
                more = false;
            }
        }
        if (inLength > 0) {
            outBytes = cipher.doFinal(inBytes, 0, inLength);
        } else {
            outBytes = cipher.doFinal();
        }
        out.write(outBytes);
    }

    /**
     * 生一个长度为len的随机数
     *
     * @param len 生成的随机密码的长度
     * @return String pwd 返回生成的随机密码
     */
    public static String getRandomKey(int len) {
        if (len >= 0) {
            Random r = new Random();
            StringBuilder sb = new StringBuilder();
            for (int i = 0; i < len; i++) {
                sb.append(r.nextInt(10));
            }
            return sb.toString();
        }
        return null;
    }

    private static SecretKeySpec secretKeySpec(String key) throws Exception {
        SecureRandom random = SecureRandom.getInstance(SHA1PRNG);
        random.setSeed(key.getBytes(UTF_8));
        KeyGenerator kgen = KeyGenerator.getInstance(AES);
        kgen.init(128, random);
        SecretKeySpec skeySpec = new SecretKeySpec(kgen.generateKey().getEncoded(), AES);
        return skeySpec;
    }

    /**
     * 根据key计算IV
     *
     * @param str
     * @return
     */
    public static String generateIV(String str) {
        byte[] bytes = str.getBytes();
        Long value = 1L;
        for (int i = 0; i < bytes.length; i++) {
            byte b = bytes[i];
            if (b != 0) {
                if (i % 2 == 0) {
                    value = value + (b & 0xff);
                } else {
                    value = value * (b & 0xff);
                }
                if (value > 999999999999999L) {
                    break;
                }
            }
        }
        if (value < 999999999999999L) {
            do {
                value <<= str.length();
            } while (value < 999999999999999L);
        }
        String result = String.valueOf(value);
        return result.length() > 16 ? result.substring(0, 16) : result;
    }

    public static void main(String[] args) {
        Date start = DateUtil.nowDate();
        for (int i = 0; i < 100; i++) {
            System.out.println(generateIV("123456"));
        }
        Date end = DateUtil.nowDate();
        System.out.println(DateUtil.betweenDate(end, start, ChronoUnit.MILLIS));
    }
}
